# coding:utf-8
# F5
import paramiko
import re
from os import devnull
from network.base_connection import BaseConnection
from network.CustomizedException import *
paramiko.util.log_to_file(devnull, level=paramiko.common.WARNING)


class F5(BaseConnection):

    """创建和销毁ssh会话"""

    def _set_pagination_param(self):
        """
        设置设备分页相关的默认参数，若用户指定了相关参数，则优先使用用户设置的参数
        分页标识符仅在每次从缓冲区读取的命令回显的最后一行中查找
        """
        if not self.no_pagination_cmd:
            # 取消分页显示的命令
            self.no_pagination_cmd = "modify cli preference pager disable display-threshold 0"
        if self.logger:
            self.logger.debug(f"no_pagination_cmd:`{self.no_pagination_cmd}`")
        if not self.pagination_pattern:
            # 分页标识
            self.pagination_pattern = re.compile(r"---\(less \d+%\)---|--More--")   # less为TMSH分页标识，More为LinuxBash分页标识
        if self.logger:
            self.logger.debug(f"pagination_pattern:`{self.pagination_pattern}`")
        if not self.next_page_cmd:
            # 分页模式下获取下一页的命令
            self.next_page_cmd = " "
        if self.logger:
            self.logger.debug(f"next_page_cmd:`{self.next_page_cmd}`")

    def _set_prompt_patterns(self):
        """
        设置已知的命令提示符正则模式元组
        命令提示符仅在每次从缓冲区读取的命令回显的最后一行中查找
        """
        patterns = {
            # Linux bash
            re.compile(r"^Password:\s*$"),
            re.compile(r".*\$\s*$"),
            re.compile(r".*#\s*$"),
            # F5 tmsh
            re.compile(r"^\(END\)\s*$"),    # 分页模式下，回显结束标识，需q退出
        }
        if self.pagination_pattern:
            patterns.add(self.pagination_pattern)
        if self.prompt_patterns:
            patterns.update(self.prompt_patterns)
        self.prompt_patterns = patterns
        if self.logger:
            self.logger.debug(f"prompt_patterns:{self.prompt_patterns}")

    def _set_error_patterns(self):
        """
        设置已知的命令错误提示正则模式元组
        错误提示符会在命令回显的每行中查找，所以需要修饰符re.M
        """
        patterns = {
            # Linux bash
            re.compile(r"^-bash: .*: command not found\s*$", re.M),
            re.compile(r"No such file or directory\s*$", re.M),
            re.compile(r"Permission denied\s*$", re.M),
            # F5 tmsh
            re.compile(r"^Syntax Error:", flags=re.M),  # TMSH
            re.compile(r"^Data Input Error:", flags=re.M)   # TMSH
        }
        if self.error_patterns:
            patterns.update(self.error_patterns)
        self.error_patterns = patterns
        if self.logger:
            self.logger.debug(f"error_patterns:{self.error_patterns}")

    """视图切换"""

    def change_mode(self, mode):
        """
        更改命令模式
        :param str mode:命令模式，可选参数: enable|user
        enable    Linux Bash Shell(bash)
        user      Traffic Manage Shell(tmsh)
        config    F5的配置模式有多种module，所以不支持
        """
        # 判断当前所处命令视图
        res = self.shell("")
        if "(tmos.)#" in res:
            current = "config"
        elif "(tmos)#" in res:
            current = "user"
        else:
            current = "enable"
        # 切换命令视图
        if mode.lower() == "enable":
            if current == "config" or current == "user":
                self.shell("quit")
            if self.logger:
                self.logger.info(f"change mode to {mode}")
        elif mode.lower() == "user":
            if current == "enable":
                cmd = "tmsh" if self.is_root else "sudo tmsh"
                self.shell(cmd)
            if current == "config":
                self.shell("quit")
                cmd = "tmsh" if self.is_root else "sudo tmsh"
                self.shell(cmd)
            if self.logger:
                self.logger.info(f"change mode to {mode}")
        elif mode.lower() == "config":
            msg = "F5‘s config mode include multiple module, so it's not supported"
            if self.logger:
                self.logger.warning(msg)
            raise UnSupportedException(msg)
        else:
            msg = "unknown mode `{}`, the valid mode is `enable` or `user`".format(mode)
            if self.logger:
                self.logger.warning(msg)
            raise UnSupportedException(msg)

    """读写channel"""
    def shell(self, cmd, loop_delay=0.5, strict=False, cmd_error=True):
        """
        执行命令并进行结果检查，命令错误则抛出异常，否则返回结果
        :param str cmd:要执行的命令
        :param float loop_delay: 延迟时间
        :param bool strict: 是否严格执行命令提示符匹配
            True    无法匹配命令提示符时引发超时异常
            False   无法匹配命令提示符时仍会返回结果
        :param bool cmd_error: 是否对命令结果进行异常检查
        """
        if self.logger:
            self.logger.info(f"try to execute command:`{cmd}`, with param:loop_delay={loop_delay}, strict={strict}, cmd_error={cmd_error}")
        # 读写channel
        self._write_channel(cmd)
        if self.pagination_enable:
            if not self.pagination_pattern or not self.next_page_cmd:
                msg = "host is in pagination mode,but pagination_pattern or next_page_cmd is None"
                if self.logger:
                    self.logger.error(msg)
                raise PreconditionException(msg)
            res = ""
            while True:
                sub_res = self._read_channel(loop_delay, strict)
                sub_res_split = sub_res.split(self.response_return)
                if self.pagination_pattern.findall(sub_res_split[-1]):
                    if self.logger:
                        self.logger.info("matched pagination pattern success, get next page of command result")
                    sub_res = self.response_return.join(sub_res_split[0:-1])
                    res += sub_res
                    self._write_channel(self.next_page_cmd)
                elif re.findall(r"^\s*\(END\)\r\r\(END\)\s*$", sub_res_split[-1]):
                    if self.logger:
                        self.logger.info("matched EOF")
                    sub_res = self.response_return.join(sub_res_split[0:-1])
                    res += sub_res
                    self._write_channel("q")
                else:
                    if self.logger:
                        self.logger.info("no pagination pattern matched, get command result finish")
                    res += sub_res
                    break
        else:
            res = self._read_channel(loop_delay, strict)
        # 检查是否存在命令错误
        if cmd_error:
            self._find_error(res)
        # 返回命令回显
        if self.logger:
            self.logger.info(f"return result of command '`{cmd}`'")
        return res

    def shell_search_set(self, cmds, ignore=False, loop_delay=0.5, strict=False, cmd_error=True):
        """
        执行一组查询命令并进行结果检查，若任一命令错误则抛出异常，否则返回一组结果命令回显
        :param [str] cmds:要执行的命令集合
        :param bool ignore: 是否忽略错误
            True    当前命令出错不会影响下一条命令的执行
            False   当前命令出错，终止剩余命令的执行
        :param float loop_delay: 延迟时间
        :param bool strict: 是否严格执行命令提示符匹配
            True    无法匹配命令提示符时引发超时异常
            False   无法匹配命令提示符时仍会返回结果
        :param bool cmd_error: 是否对命令结果进行异常检查
        """
        if self.logger:
            self.logger.info(f"try to execute a set of search commands with param:ignore={ignore}, loop_delay={loop_delay}, strict={strict}, cmd_error={cmd_error}.\ncommands:{cmds}")
        self.change_mode("enable")
        while cmds:
            cmd = cmds.pop(0)
            try:
                res = self.shell(cmd=cmd, loop_delay=loop_delay, strict=strict, cmd_error=cmd_error)
                yield {"state": True, "res": res, "message": ""}
            except Exception as e:
                yield {"state": False, "res": "", "message": repr(e)}
                if not ignore:
                    if self.logger:
                        self.logger.error(f"error occur while execute command `{cmd}`, terminates execution of the command set")
                    break
        if self.logger:
            self.logger.info("search command set execute finish")

    def shell_config_set(self, cmds, loop_delay=0.5, strict=False, cmd_error=True):
        """
        执行一组查询命令并进行结果检查，若任一命令错误则抛出异常
        :param [str] cmds:要执行的命令集合
        :param float loop_delay: 延迟时间
        :param bool strict: 是否严格执行命令提示符匹配
            True    无法匹配命令提示符时引发超时异常
            False   无法匹配命令提示符时仍会返回结果
        :param bool cmd_error: 是否对命令结果进行异常检查
        """
        msg = "F5 unsupported function shell_config_set"
        if self.logger:
            self.logger.warning(msg)
        raise UnSupportedException(msg)


    """配置保存"""

    def save(self):
        """
        保存配置
        tmsh save sys ucs 20230705  # 将当前系统的配置文件保存为一个归档文件/var/local/ucs/20230705.usc
        tmsh load sys ucs 20230705.ucs   # 加载配置文件/var/local/ucs/20230705.usc
        tmsh save sys config    # 将当前配置写入文件系统
        tmsh load sys config    # 从文件系统加载配置
        tmsh save sys config partitions all # 保存所有用户分区的配置
        tmsh load sys config partitions all # 加载所有用户分区的配置
        tmsh save sys config file 20230705  # 将当前配置保存到文件/var/local/scf/20230705
        """
        try:
            self.change_mode("user")
            self.shell("save sys config partitions all")
            if self.logger:
                self.logger.info("save configuration success")
        except Exception as e:
            if self.logger:
                self.logger.error(f"save configuration failure:{str(e)}")
            raise ConfigSaveException(str(e))

    """扩展功能"""

    @property
    def platform(self):
        """
        获取设备平台(操作系统)类型, 获取不到则返回None
        目前已支持类型：BIG-IP
        """
        # 获取平台类型（操作系统类型）
        if not hasattr(self, "__platform"):
            self.__platform = None
            self.change_mode("user")
            res = self.shell("show sys version")
            patterns = [
                re.compile(r"Product\s+(\S+)", re.M),
            ]
            for pattern in patterns:
                platform = pattern.findall(res)
                if platform:
                    self.__platform = platform[0]
                    break
        if self.logger:
            self.logger.debug(f"return __platform: {self.__platform}")
        return self.__platform

    @property
    def is_root(self):
        """
        判断当前登录账号是否为root权限
        """
        # 获取平台类型（操作系统类型）
        if not hasattr(self, "__is_root"):
            self.change_mode("enable")
            res = self.shell("whoami")
            if re.findall(r"^root$", res, re.M):
                self.__is_root = True
            else:
                self.__is_root = False
        if self.logger:
            self.logger.debug(f"return __is_root:{self.__is_root}")
        return self.__is_root


